/*	--- 使用ThreadLocal --- 


【多线程】是Java实现‘多任务’的基础。

【Thread对象】代表【一个线程】，我们可以在代码中调用Thread.currentThread()获取当前线程。

例如，打印日志时，可以同时打印出当前线程的名字： 
	Thread.currentThread().getName() */
public class Main {
	public static void main(String[] args) throws Exception {
		log("start main...");

		new Thread(() -> {
			log("run task...");
		}).start();

		new Thread(() -> {
			log("print...");
		}).start();

		log("end main.");
	}

	static void log(String s) {
		System.out.println(Thread.currentThread().getName() + ": " + s);
	}
}


/*
对于多任务，Java标准库提供的【线程池】可以方便地执行这些任务，同时复用线程。

【Web应用程序】就是典型的【多任务应用】，‘每个用户’请求页面时，我们都会创建一个【任务】，类似： */
public void process(User user) {
    checkPermission();
    doWork();
    saveStatus();
    sendResponse();
}


/*
然后，通过【线程池】去执行这些任务。
(一个User = 一个任务)

----------------------------------


观察process()方法，它内部需要调用若干其他方法，同时，我们遇到一个问题：如何在一个线程内传递状态？

process()方法需要传递的状态就是User实例。

有的童鞋会想，简单地传入User就可以了：  */
public void process(User user) {
    checkPermission(user);
    doWork(user);
    saveStatus(user);
    sendResponse(user);
}


// 但是往往一个方法又会调用其他很多方法，这样会导致User传递到所有地方：
void doWork(User user) {
    queryStatus(user);
    checkStatus();
    setNewStatus(user);
    log();
}


/*
这种在【一个线程】中，横跨若干方法调用，需要传递的对象，我们通常称之为【上下文(Context)】, 它是【一种状态】, 可以是【用户身份、任务信息等】。

-----------------------

给每个方法增加一个【context参数】非常麻烦。

而且有些时候，如果调用链有无法修改源码的第三方库，User对象就传不进去了。

--------

Java标准库提供了一个特殊的ThreadLocal,它可以在【一个线程中】传递【同一个对象】。

【ThreadLocal实例】通常总是以【static静态初始化】

如下：  */
static ThreadLocal<User> threadLocalUser = new ThreadLocal<>();



// 它的典型使用方式如下：
void processUser(user) { // process v. 处理,加工,审核
    try {
        threadLocalUser.set(user); // 设置通信变量
        step1();
        step2();
    } finally {
        threadLocalUser.remove();
    }
}


// 通过设置一个【User实例】关联到ThreadLocal中，在移除之前，所有方法都可以随时获取到该User实例：
void step1() {
    User u = threadLocalUser.get(); // 获取通信变量
    log();
    printUser();
}

void log() {
    User u = threadLocalUser.get();
    println(u.name);
}

void step2() {
    User u = threadLocalUser.get();
    checkUser(u.id);
}



/*
注意到普通的方法调用一定是【同一个线程】执行的。

所以，step1()、step2()以及log()方法内, 【threadLocalUser.get()】获取的【User对象】是【同一个实例】。


实际上,可以把ThreadLocal看成 【一个全局Map<Thread, Object>】:
【每个线程】获取【ThreadLocal变量】时,总是使用【Thread自身】作为key: */
Object threadLocalValue = threadLocalMap.get(Thread.currentThread());


/*
因此,【ThreadLocal】相当于给每个线程都开辟了【一个独立的存储空间】,各个线程的ThreadLocal关联的实例互不干扰。

最后，特别注意ThreadLocal一定要在finally中清除：  */
try {
    threadLocalUser.set(user);
    ...
} finally {
    threadLocalUser.remove(); // finally中消除
}


/*
这是因为【当前线程】执行完相关代码后,很可能会被重新放入线程池中.

如果ThreadLocal没有被清除,该线程执行其他代码时,会把上一次的【状态context】带进去。

-----------------

为了保证能释放ThreadLocal关联的实例,我们可以通过AutoCloseable接口配合try (resource) {...}结构,让编译器 自动为我们关闭。

例如，一个保存了当前用户名的ThreadLocal可以封装为一个【UserContext对象】:  */

// ThreadLocal封装
public class UserContext implements AutoCloseable {

    static final ThreadLocal<String> ctx = new ThreadLocal<>(); // ctx = Context

    public UserContext(String user) {
        ctx.set(user);
    }

    public static String currentUser() { 
        return ctx.get();
    } // UserContext.currentUser()

    @Override
    public void close() {
        ctx.remove();
    } // Auto
}



// 使用ThreadLocal的封装的时候, 我们尽可能借助try (resource) {...}结构，可以这么写：
try (var ctx = new UserContext("Bob")) {
    // 可任意调用UserContext.currentUser():
    String currentUser = UserContext.currentUser();
} // 在此自动调用UserContext.close()方法释放ThreadLocal关联对象


/*
这样就在UserContext中完全封装了ThreadLocal.

外部代码在try (resource) {...}的内部中, 可以随时调用UserContext.currentUser()获取当前线程绑定的用户名。  */




/*---------------------------------------------

#	---- 使用ThreadLocal の 小结 ---- 

1. ThreadLocal表示线程的“局部变量”，它确保每个线程的ThreadLocal变量都是各自独立的；

2. ThreadLocal适合在一个线程的处理流程中保持【Context上下文】
	(避免了同一参数在所有方法中传递);

3. 使用ThreadLocal要用try... finally结构,并在finally中清除。

4. 实际上，可以把ThreadLocal看成一个全局Map<Thread, Object>：每个线程获取ThreadLocal变量时，总是使用Thread自身作为key

模板 */
public class UserContext implements AutoCloseable {

    static final ThreadLocal<String> ctx = new ThreadLocal<>();


    public UserContext(String user) { // 构造函数
        ctx.set(user);
    }

    public static String currentUser() {
        return ctx.get();
    }

    @Override
    public void close() {
        ctx.remove();
    }
}

// 使用的时候
try (var ctx = new UserContext("Bob")) {
    // 可任意调用UserContext.currentUser():
    String currentUser = UserContext.currentUser();
} // 在此自动调用UserContext.close()方法释放ThreadLocal关联对象






















